// The MIT License

// Copyright (c) 2009 Massachusetts Institute of Technology

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Local includes
`include "asim/provides/low_level_platform_interface.bsh"
`include "asim/provides/soft_connections.bsh"
`include "asim/provides/processor_library.bsh"
`include "asim/provides/fpga_components.bsh"
`include "asim/provides/common_services.bsh"
//`include "asim/dict/STATS_DATA_CACHE.bsh"
`include "asim/provides/mem_services.bsh"

`include "asim/provides/scratchpad_memory.bsh"
`include "asim/dict/VDEV_SCRATCH.bsh"

import Connectable::*;
import GetPut::*;
import ClientServer::*;
import RegFile::*;
import FIFO::*;
import FIFOF::*;




interface DCache#( type req_t, type resp_t );

  // Interface from processor to cache
  interface Server#(req_t,resp_t) proc_server;

  // Interface for enabling/disabling statistics
//  interface Put#(Bool) statsEn_put;

endinterface


//----------------------------------------------------------------------
// Cache Types
//----------------------------------------------------------------------

typedef 20   CacheLineIndexSz;
typedef 12   CacheLineTagSz;
typedef 32   CacheLineSz;

typedef Bit#(CacheLineIndexSz) CacheLineIndex;
typedef Bit#(CacheLineTagSz)   CacheLineTag;
typedef Bit#(CacheLineSz)      CacheLine;

typedef enum 
{ 
  Init,
  Access,
	AccessResp
 // RefillReq, 
 // RefillResp 
} 
CacheStage 
deriving (Eq,Bits);

//----------------------------------------------------------------------
// Helper functions
//----------------------------------------------------------------------

function Bit#(AddrSz) getAddr( DataReq req );

  Bit#(AddrSz) addr = ?;
  case ( req ) matches
    tagged LoadReq  .ld : addr = ld.addr;
    tagged StoreReq .st : addr = st.addr;
  endcase

  return addr;

endfunction

function Bit#(3) getSize( DataReq req );
	Bit#(3) size = ?;
	case ( req ) matches
		tagged LoadReq .ld : size = ld.size;
		tagged StoreReq .st : size = st.tag[2:0];
	endcase

	return size;
endfunction

function CacheLineIndex getCacheLineIndex( DataReq req );
  Bit#(AddrSz) addr = getAddr(req);
  Bit#(CacheLineIndexSz) index = truncate( addr >> 2 );
  return index;
endfunction

function CacheLineTag getCacheLineTag( DataReq req );
  Bit#(AddrSz)         addr = getAddr(req);
  Bit#(CacheLineTagSz) tag  = truncate( addr >> fromInteger(valueOf(CacheLineIndexSz)) >> 2 );
  return tag;
endfunction

function Bit#(AddrSz) getCacheLineAddr( DataReq req );
  Bit#(AddrSz) addr = getAddr(req);
  return ((addr >> 2) << 2);
endfunction

//----------------------------------------------------------------------
// Main module
//----------------------------------------------------------------------

module [CONNECTED_MODULE] mkDataCache( DCache#(DataReq,DataResp) );

  //-----------------------------------------------------------
  // State

  Reg#(CacheStage) stage <- mkReg(Init);

//  LUTRAM#(CacheLineIndex,Maybe#(CacheLineTag)) cacheTagRam  <- mkLUTRAMU_RegFile();
//  LUTRAM#(CacheLineIndex,CacheLine)            cacheDataRam <- mkLUTRAMU_RegFile();
//  BRAM#(CacheLineIndex,Maybe#(CacheLineTag)) cacheTagBRam  <- mkBRAM();
//  BRAM#(CacheLineIndex,CacheLine)            cacheDataBRam <- mkBRAM();
	MEMORY_IFC#(CacheLineIndex, Maybe#(CacheLineTag)) cacheTagBRam <- mkScratchpad(`VDEV_SCRATCH_DCACHE_TAG, SCRATCHPAD_CACHED);
	MEMORY_IFC#(CacheLineIndex, CacheLine) cacheDataBRam <- mkScratchpad(`VDEV_SCRATCH_DCACHE_DATA, SCRATCHPAD_CACHED);

  FIFO#(DataReq)   reqQ  <- mkFIFO();
  FIFOF#(DataResp) respQ <- mkBFIFOF1();

  Reg#(CacheLineIndex) initCounter <- mkReg(1);

  // Statistics state

//  Reg#(Bool)     statsEn        <- mkReg(False);
//  STAT num_accesses <- mkStatCounter(`STATS_DATA_CACHE_NUM_ACCESSES);
//  STAT num_misses <- mkStatCounter(`STATS_DATA_CACHE_NUM_MISSES);
//  STAT num_writebacks <- mkStatCounter(`STATS_DATA_CACHE_NUM_WRITEBACKS);
//	STAT num_accesses <- mkStatCounter(statName("DATA_CACHE_NUM_ACCESSES", "DATA_CACHE: Number Of Accesses: "));
//	STAT num_misses <- mkStatCounter(statName("DATA_CACHE_NUM_MISSES", "DATA_CACHE: Number Of Misses: "));
//	STAT num_writebacks <- mkStatCounter(statName("DATA_CACHE_NUM_WRITEBACKS", "DATA_CACHE: Number Of Writebacks: "));

  //-----------------------------------------------------------
  // Name some wires

  let req              = reqQ.first();
  let reqIndex         = getCacheLineIndex(req);
  let reqTag           = getCacheLineTag(req);
  let reqCacheLineAddr = getCacheLineAddr(req);

  //-----------------------------------------------------------
  // Initialize

  rule init ( stage == Init );
//    traceTiny("mkDataCacheBlocking", "stage","i");
    initCounter <= initCounter + 1;
//    cacheTagRam.upd(initCounter,Invalid);
    cacheTagBRam.write(initCounter,Invalid);
    if ( initCounter == 0 )
      stage <= Access;
  endrule
   
  //-----------------------------------------------------------
  // Access cache rule
	rule accessReq (stage == Access);
    //reqQ.deq(); // DO NOT deq HERE! deq at resp
		cacheTagBRam.readReq(reqIndex);
		cacheDataBRam.readReq(reqIndex);
		stage <= AccessResp;
//		$display ( "accessReq" );
	endrule

	rule accessResp (stage == AccessResp && respQ.notFull());
    reqQ.deq(); 
//		$display ( "accessResp" );
    
		CacheLine cacheLineData <- cacheDataBRam.readRsp();
		Maybe#(CacheLineTag) cacheLineTag <- cacheTagBRam.readRsp();
    if ( isValid(cacheLineTag) && ( unJust(cacheLineTag) == reqTag ) )
		begin
			let addr = getAddr( req );
			case ( req ) matches 
				tagged LoadReq .ld : begin 
					if ( getSize ( req ) == 4 ) 
						respQ.enq( LoadResp { tag: ld.tag, data: cacheLineData  } );
					else if ( getSize ( req ) == 2 )
						begin
							if ( addr[1] == 1 )
								respQ.enq( LoadResp { tag: ld.tag, data: {0, cacheLineData[31:16]}  } );
							else
								respQ.enq( LoadResp { tag: ld.tag, data: zeroExtend(cacheLineData[15:0])  } );
								
						end
					else if ( getSize ( req ) == 1 )
						begin
							if ( addr[1:0] == 0 )
								respQ.enq( LoadResp { tag: ld.tag, data: zeroExtend( cacheLineData[7:0])  } );
							else if ( addr[1:0] == 1 )
								respQ.enq( LoadResp { tag: ld.tag, data: zeroExtend( cacheLineData[15:8])  } );
							else if ( addr[1:0] == 2 )
								respQ.enq( LoadResp { tag: ld.tag, data: zeroExtend( cacheLineData[23:16])  } );
							else if ( addr[1:0] == 3 )
								respQ.enq( LoadResp { tag: ld.tag, data: zeroExtend( cacheLineData[31:24]) } );
						end
						
					end
				 tagged StoreReq .st : begin
				 	if ( getSize ( req ) == 4)
						cacheDataBRam.write(reqIndex,st.data);
					else if ( getSize ( req ) == 2 )
					begin
						if ( addr[1] == 0 )
							cacheDataBRam.write(reqIndex,{cacheLineData[31:16], st.data[15:0] });
						else
							cacheDataBRam.write(reqIndex,{st.data[31:16], cacheLineData[15:0] });
					end
					else if (getSize ( req ) == 1 )
					begin
						if ( addr[1:0] == 0 )
							cacheDataBRam.write(reqIndex,{ cacheLineData[31:8], st.data[7:0] });
						else if ( addr[1:0] == 1 )
							cacheDataBRam.write(reqIndex,{ cacheLineData[31:16], st.data[15:8], cacheLineData[7:0] });
						else if ( addr[1:0] == 2 )
							cacheDataBRam.write(reqIndex,{ cacheLineData[31:24], st.data[23:16], cacheLineData[15:0] });
						else if ( addr[1:0] == 3 )
							cacheDataBRam.write(reqIndex,{ st.data[31:24], cacheLineData[23:0] });
					end
				end
			endcase
		end
		else 
      begin
	 			cacheTagBRam.write(reqIndex,Valid(reqTag));
        case ( req ) matches
          tagged LoadReq .ld :
						begin
						respQ.enq( LoadResp { tag: ld.tag, data: 0 } );
						cacheDataBRam.write(reqIndex,0);
						end
          tagged StoreReq .st :
						begin
						cacheDataBRam.write(reqIndex,st.data);
						//TODO
						end
				endcase
      end
			stage <= Access;
	endrule
	
	//-----------------------------------------------------------
  // Methods

  interface Server proc_server;
    //interface Put request  = tracePut("mkDataCacheBlocking", "reqTiny",fifoToPut(reqQ));
    //interface Get response = traceGet("mkDataCacheBlocking", "respTiny",fifofToGet(respQ));
    interface Put request  = fifoToPut(reqQ);
    interface Get response = fifofToGet(respQ);
  endinterface

//  interface Put statsEn_put = regToPut(statsEn);

endmodule
